// HSS.Net.Mail.SmtpConnection.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license
// http://www.opensource.org/licenses/mit-license.html
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       SmtpConnection.cs
// Author:     HSS\gbanta
// Created:    08/12/2010
// Modified:   12/04/2010
// ----------------------------------------------------------------------------
namespace HSS.Net.Mail
{
	#region Using Directives
	using System;
	using System.Text;
	using System.IO;
	using System.Net.Sockets;
	using System.Net;
	using System.Net.Mail;
	using System.Diagnostics;
	using System.Collections;
	using System.Collections.Generic;
	#endregion

	#region SmtpConnection
	internal class SmtpConnection : IDisposable
	{
		private Socket s;

		internal SmtpConnection()
		{
			s = null;
		}
		internal SmtpConnection(string[] smtpServer)
		{
			this.Connect(smtpServer);
		}

		internal void Connect(string[] smtpServer)
		{
			if (null != s)
				throw new SmtpException(Resources.SmptConnectAlreadyExist);

			bool connected = false;
			int smtpIndex = 0;
			s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
			s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 3000);
			while (!connected)
			{
				string smtp = string.Empty;
				if (smtpIndex < smtpServer.Length)
				{
					smtp = smtpServer[smtpIndex];
					IPHostEntry IPhst = System.Net.Dns.GetHostEntry(smtp);
					IPEndPoint endPt = new IPEndPoint(IPhst.AddressList[0], 25);
					try
					{
						s.Connect(endPt);
						connected = true;
						Console.WriteLine(string.Format("Connected to {0}", smtp));
					}
					catch
					{
						connected = false;
						Console.WriteLine(string.Format("Failed to connect to {0}", smtp));
					}
				}
				else
				{
					Console.WriteLine("Failed to connect to a valid Smtp Server");
					throw new SmtpException("Failed to connect to a valid Smtp Server");
				}
				smtpIndex++;
			}

			SmtpStatusCode responseCode;
			string response;

			this.GetResponse(out responseCode, out response);

			if (responseCode != SmtpStatusCode.ServiceReady)
			{
				Console.WriteLine(response);
				s.Close();
				throw new SmtpException(responseCode, response);
			}

		}

		internal void Disconnect()
		{
			if (null != s)
			{
				if (StatusCode == SmtpStatusCode.ServiceNotAvailable)
				{
					// Server has already disconnected us, so just close
					try
					{
						s.Close();
						s = null;
					}
					catch { }
					return;
				}

				SmtpStatusCode responseCode;
				string response;

				this.buffer.Clear();
				this.buffer.Append(SmtpCommands.Quit);

				this.Send();
				this.GetResponse(out responseCode, out response);

				if (responseCode != SmtpStatusCode.ServiceClosingTransmissionChannel)
					Console.WriteLine(response);

				System.Threading.Thread.Sleep(100);

				try
				{
					s.Close();
					s = null;
				}
				catch { }
			}
		}

		internal void Send()
		{
			if (null == s || !s.Connected)
			{
				throw new SmtpException("Missing a valid and open SmtpConnection.");
			}
			int bytesSent = s.Send(this.buffer.GetBuffer(), 0, this.buffer.Length, SocketFlags.None);
			Console.WriteLine(string.Format("Sent bytes[{0}]: {1}", bytesSent, this.buffer.ToString()));
			this.buffer.Clear();
		}

		internal void GetResponse(out SmtpStatusCode responseCode, out string response)
		{

			if (this.buffer.Length > 0)
				this.buffer.Clear();

			var lastResponseCode = SmtpStatusCode.GeneralFailure;
			responseCode = SmtpStatusCode.GeneralFailure;
			response = string.Empty;
			int attempts = 0;
			int maxAttempts = 250, sleep = 250; // Roughly 1 minute
			int retries = 0, maxRetries = 5; // Roughly 5 minute
			try
			{
				while (s.Available == 0 && retries < maxRetries)
				{
					while (s.Available == 0 && attempts < maxAttempts)
					{
						System.Threading.Thread.Sleep(sleep);
						attempts++;
					}
					if (s.Available > 0)
						break;
					attempts = 0;
					retries++;
					Console.WriteLine(string.Format("Waiting for response. Retry: {0}", retries));
				}

				if (s.Available == 0)
					throw new SmtpException("Failed to read the response form the Server.");

				attempts = 0;
				while (s.Available > 0)
				{
					int available = s.Available;
					byte[] bytes = new byte[available];
					int bytesRead = s.Receive(bytes, 0, available, SocketFlags.None);
					if (bytesRead == 0)
						throw new SmtpException(HSS.Net.Mail.Resources.SmtpMissingResponse);
					this.buffer.Append(bytes);
					System.Threading.Thread.Sleep(sleep);
				}

				response = this.buffer.ToString().Trim('\0');
				if (response.Length > 0)
				{
					// Some email servers return more than one response message in the same response
					// Look for a second line and parse that response code.
					string[] multiResponse = response.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
					if (multiResponse.Length > 1)
					{
						// First response, is the last request's Response
						responseCode = (SmtpStatusCode)Convert.ToInt32(response.Substring(0, 3));

						// Last response is Connection Oriented Response
						string lastResponseString = multiResponse[multiResponse.Length - 1];
						lastResponseCode = (SmtpStatusCode)Convert.ToInt32(lastResponseString.Substring(0, 3));
					}
					else
					{
						responseCode = (SmtpStatusCode)Convert.ToInt32(response.Substring(0, 3));
						lastResponseCode = responseCode;
					}
				}
				Console.WriteLine(string.Format("Response: {0}", response));
			}
			finally
			{
				this.buffer.Clear();
				_statusCode = lastResponseCode;
			}
		}

		internal BufferBuilder Buffer
		{
			get { return buffer; }
		} BufferBuilder buffer = new BufferBuilder();

		internal SmtpStatusCode StatusCode
		{
			get { return _statusCode; }
		} SmtpStatusCode _statusCode;

		#region IDisposable Members

		public void Dispose()
		{
			this.Disconnect();
		}

		#endregion
	}
	#endregion
}